package org.ftc.web;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.ftc.domain.Motion;
import org.ftc.domain.OpenTime;
import org.ftc.domain.Session;
import org.ftc.enums.Constant;
import org.ftc.enums.FileType;
import org.ftc.form.FileForm;
import org.ftc.form.MotionForm;
import org.ftc.form.SessionForm;
import org.ftc.form.TreeForm;
import org.ftc.service.FtcLogService;
import org.ftc.service.FtcLogService.Log;
import org.ftc.service.MotionService;
import org.ftc.service.OpenTimeService;
import org.ftc.service.SessionService;
import org.ftc.service.UsersService;
import org.ftc.util.Global;
import org.ftc.util.ListCalculator;
import org.joda.time.DateTime;
import org.joda.time.Months;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SessionViewController {
	@Autowired
	UsersService usersService;
	@Autowired
	SessionService sessionService;
	@Autowired
	MotionService motionService;
	@Autowired
	OpenTimeService openTimeService;
	@Autowired
	FtcLogService logService;

	/**
	 * 會期瀏覽
	 * 
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/sessionBrowse.htm")
	public String index(ModelMap model) {
		model.addAttribute("searchForm", new SessionForm());//
		return "sessionView/browse";
	}

	/**
	 * 會期查詢列表
	 * 
	 * @param form
	 * @param binding
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/sessionBrowse/list.json", method = RequestMethod.POST)
	public @ResponseBody List<TreeForm> browseList(SessionForm form, BindingResult binding, ModelMap model) {

		List<Session> sessions = sessionService.findAll();

		Comparator<Session> byYear = (s1, s2) -> Integer.compare(NumberUtils.toInt(s1.getYear()),NumberUtils.toInt(s2.getYear()));
		Comparator<Session> byLength = (s1, s2) -> Integer.compare(s1.getSessionNo().length(), s2.getSessionNo()
				.length());
		Comparator<Session> bySessionNo = (s1, s2) -> s1.getSessionNo().compareTo(s2.getSessionNo());
		Map<String, List<TreeForm>> map = sessions.stream().parallel(). //
				sorted(byYear.thenComparing(byLength).thenComparing(bySessionNo)) //
				.collect(Collectors.groupingBy( //
						Session::getYear, //
						LinkedHashMap::new, //
						Collectors.mapping(Session::transferTree, Collectors.toList()) //
						));
		Set<Entry<String, List<TreeForm>>> set = map.entrySet();
		List<TreeForm> treeForms = new ArrayList<>();
		for (Entry<String, List<TreeForm>> entry : set) {
			TreeForm treeForm = new TreeForm();
			treeForm.setId(entry.getKey());
			treeForm.setText(entry.getKey() + "年");
			treeForm.setState("closed");
			treeForm.setChildren(entry.getValue());
			treeForms.add(treeForm);
		}

		return treeForms;
	}

	/**
	 * 會期查詢
	 * 
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/sessionView.htm")
	public String search(ModelMap model) {
		model.addAttribute("searchForm", new SessionForm());//
		return "sessionView/list";
	}

	/**
	 * 會期查詢列表
	 * 
	 * @param form
	 * @param binding
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/sessionView/list.json", method = RequestMethod.POST)
	public @ResponseBody Map<String, Object> list(SessionForm form, BindingResult binding, ModelMap model) {
		logService.before();
		model.put("searchForm", form);
		Map<String, Object> map = new HashMap<>();
		ListCalculator listCal = new ListCalculator();
		int pageSize = form.getRows();
		listCal.setRecordsPerPage(pageSize);
		Integer totalRecs = (int) sessionService.count(form);
		int totalPages = listCal.totalPages(totalRecs);
		int pageNo = form.getPage();
		pageNo = (pageNo >= totalPages) ? totalPages : pageNo;
		int firstRes = listCal.firstResult(pageNo);
		map.put("total", totalRecs.toString());
		List<Session> datas = sessionService.findPage(form, firstRes, pageSize);
		map.put("rows", datas);
		logService.save(Log.QUERY, this.getClass().toString());
		return map;
	}

	/**
	 * 是否已開放
	 * 
	 * @param sessionNo
	 * @param motionNo
	 * @return
	 */
	@RequestMapping(value = "/sessionView/isOpen/{sessionNo}.json", method = RequestMethod.GET)
	public @ResponseBody boolean isOpen(@PathVariable String sessionNo) {
		Session session = sessionService.get(sessionNo);
		// 該會期日期
		DateTime meetDate = new DateTime(session.getMeetingDate());
		// 系統設定期限
		OpenTime openTime = openTimeService.get(Constant.MONTH.name());
		// 兩者相距月份數
		Months month = Months.monthsBetween(meetDate, DateTime.now());
		// 大於設定或預設值6則true
		return month.getMonths() >= NumberUtils.toInt(openTime.getMonth(), 6);
	}

	/**
	 * 會期目次表
	 * 
	 * @param sessionNo
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/motionView/{sessionNo}.htm")
	public String motionIndex(@PathVariable String sessionNo, ModelMap model) {
		FileForm fileForm = new FileForm();
		fileForm.setSessionNo(sessionNo);
		model.addAttribute("fileForm", fileForm);//
		String sNo = StringUtils.leftPad(sessionNo, 5, "0");

		final String filePath = String.format("%s\\%s\\%s", Global.APP.IMAGE_PATH,StringUtils.substring(sNo, 0, 3), sNo);
		PathMatcher PATH_MATCH = FileSystems.getDefault().getPathMatcher("glob:*.{pdf,tif}");
		Map<String, List<String>> map = new HashMap<>();
		if (Files.exists(Paths.get(filePath))) {
			try (Stream<Path> directoryStream = Files.list(Paths.get(filePath))) {
				map = directoryStream.parallel().filter(path -> PATH_MATCH.matches(path.getFileName()))
						.map(path -> path.getFileName().toString())
						.collect(Collectors.groupingBy(name -> genKey(name)));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		model.addAttribute("fileMap", map);//
		if (map != null && map.size() > 0) {
			String type = StringUtils.substringAfter(map.values().iterator().next().get(0), ".");
			model.addAttribute("type", type);
		}
		return "sessionView/motionList";
	}

	/**
	 * 會期目次表列表
	 * 
	 * @param form
	 * @param binding
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/motionView/list.json", method = RequestMethod.POST)
	public @ResponseBody Map<String, Object> motionList(FileForm form, BindingResult binding, ModelMap model) {
		logService.before();
		Map<String, Object> map = new HashMap<String, Object>();
		ListCalculator listCal = new ListCalculator();
		int pageSize = form.getRows();
		listCal.setRecordsPerPage(pageSize);
		MotionForm motionForm = new MotionForm();
		motionForm.setSessionNo(form.getSessionNo());
		Integer totalRecs = (int) motionService.count(motionForm);
		int totalPages = listCal.totalPages(pageSize);
		int pageNo = form.getPage();
		pageNo = (pageNo >= totalPages) ? totalPages : pageNo;
		int firstRes = listCal.firstResult(pageNo);
		map.put("total", totalRecs.toString());
		List<Motion> datas = motionService.findPage2(motionForm, firstRes, totalRecs);
		map.put("rows", datas);
		logService.save(Log.QUERY, this.getClass().toString());
		return map;
	}

	/**
	 * 影像檔案頁(檔案下載頁)
	 * 
	 * @param motionNo
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/motionView/motionDetail/{motionNo}.htm")
	public String motionDetail(@PathVariable String motionNo, ModelMap model) {
		Motion motion = motionService.getByMotionNo(motionNo);
		model.addAttribute("motion", motion);
		// 檔案路徑
		String sessionNo = StringUtils.leftPad(motion.getSession().getSessionNo(), 5, "0");
		
		final String filePath = String.format("%s\\%s\\%s", Global.APP.IMAGE_PATH, StringUtils.substring(sessionNo, 0, 3),sessionNo);
		// 檔案preffix
		final String matchFormat = String.format("glob:%s_*.{pdf,tif}", StringUtils.leftPad(motionNo, 8, "0"));
		PathMatcher pathMatch = FileSystems.getDefault().getPathMatcher(matchFormat);

		Map<String, String> map = new HashMap<>();
		if (Files.exists(Paths.get(filePath))) {
			try (Stream<Path> directoryStream = Files.list(Paths.get(filePath))) {

				map = directoryStream
						.parallel()
						.filter(path -> pathMatch.matches(path.getFileName()))
						.map(path -> path.getFileName().toString())
						.sorted((name1, name2) -> name1.compareTo(name2))
						.collect(
								Collectors.toMap(name -> name, name -> FileType.lookUpName(motionNo, name),
										(v1, v2) -> v1, LinkedHashMap::new));

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		model.addAttribute("fileMap", map);//
		if (map != null && map.size() > 0) {
			String type = StringUtils.substringAfter(map.keySet().iterator().next(), ".");
			model.addAttribute("type", type);
		}
		return "sessionView/imgList";
	}

	/**
	 * 檔案是否存在
	 * 
	 * @param sessionNo
	 * @param motionNo
	 * @return
	 */
	@RequestMapping(value = "/motionView/fileExists/{sessionNo}/{motionNo}.json", method = RequestMethod.GET)
	public @ResponseBody boolean fileExists(@PathVariable String sessionNo, @PathVariable String motionNo) {
		boolean exist = false;
		final PathMatcher fileMatch = FileSystems.getDefault().getPathMatcher(
				String.format("glob:%s*.{pdf,tif}", StringUtils.leftPad(motionNo, 8, "0")));
		
		String sNo = StringUtils.leftPad(sessionNo, 5, "0");
		
		final String filePath = String.format("%s\\%s\\%s", Global.APP.IMAGE_PATH, StringUtils.substring(sNo, 0, 3),sNo);
		if (Files.exists(Paths.get(filePath))) {
			try (Stream<Path> directoryStream = Files.list(Paths.get(filePath))) {
				exist = directoryStream.parallel().anyMatch(path -> fileMatch.matches(path.getFileName()));
			} catch (IOException e) {
				e.printStackTrace();
				return false;
			}
		}
		return exist;
	}

	@InitBinder
	public void initBinder(WebDataBinder binder) {
		SimpleDateFormat dateFormat = new SimpleDateFormat(Global.APP.DATE_FORMAT);
		dateFormat.setLenient(false);
		binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
	}

	String genKey(String path) {
		if (StringUtils.contains(path, "schedule")) {
			return "schedule";
		} else if (StringUtils.contains(path, "all")) {
			return "all";
		} else if (StringUtils.contains(path, "r01")) {
			return "r01";
		} else {
			return StringUtils.substring(path, 5, 8);
		}
	}

	String genKey(String motionNo, String name) {
		return FileType.lookUpName(motionNo, name);
	}
	
}
